<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/copy/">
<meta property="og:title" content="       从经典问题来看 Copy 方法 | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       从经典问题来看 Copy 方法 | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/copy/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>从经典问题来看 Copy 方法</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2016-08-11"> 2016-08-11
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>在初学 iOS 的时候，可能会被灌输这么一个常识，<strong>切记 NSString 的 property 的修饰变量要写作 copy ，而不是 strong</strong>，那么这是为什么？</p>
<blockquote>
  <p>经典面试题：为什么 NSString 类型成员变量的修饰属性用 copy 而不是 strong (或 retain ) ？</p>
</blockquote>
<h2 id="review-copy-operation">review Copy Operation</h2>
<h3 id="test-1">Test 1</h3>
<p>先来模拟一个程序设计错误的场景。有一个叫做 Person 的 Class，其中它拥有一个 NSString 类型的 s_name 属性（代表 name 是 strong），我们想给一个对象的 s_name 赋值，并且之前的赋值变量还想重复使用到其他场景。所以，我们在引入这个 Class 的 ViewController 进行如下操作：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">test1</span> <span class="p">{</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span> <span class="o">=</span> <span class="o">[[</span><span class="no">Person</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSMutableString</span> <span class="o">*</span><span class="nb">name</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSMutableString</span> <span class="ss">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;iOS&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">s_name</span> <span class="o">=</span> <span class="nb">name</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">s_name</span><span class="p">);</span>
    <span class="o">[</span><span class="nb">name</span> <span class="ss">appendString</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot; Source Probe&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">s_name</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>如果在 Person 这个 Class 中，我们的 s_name 的修饰属性是 <strong>strong</strong> 的话，会看到如下输出结果。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">05</span><span class="p">:</span><span class="mi">51</span><span class="p">:</span><span class="mi">21</span><span class="o">.</span><span class="mi">262</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">64714</span><span class="p">:</span><span class="mi">20449045</span><span class="o">]</span> <span class="n">iOS</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">05</span><span class="p">:</span><span class="mi">51</span><span class="p">:</span><span class="mi">21</span><span class="o">.</span><span class="mi">262</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">64714</span><span class="p">:</span><span class="mi">20449045</span><span class="o">]</span> <span class="n">iOS</span> <span class="no">Source</span> <span class="no">Probe</span></code></pre></div>
<p>可是，我们操作的仅仅是对 s_name 那个变量，为什么连属性当中的 s_name 也会被改变？对这段代码稍做修改，重新测试。</p>
<h3 id="test-2">Test 2</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">test2</span> <span class="p">{</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span> <span class="o">=</span> <span class="o">[[</span><span class="no">Person</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSString</span> <span class="o">*</span><span class="nb">name</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSMutableString</span> <span class="ss">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;iOS&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">s_name</span> <span class="o">=</span> <span class="nb">name</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">s_name</span><span class="p">);</span>
    <span class="nb">name</span> <span class="o">=</span> <span class="err">@</span><span class="s2">&quot;iOS Source Probe&quot;</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">s_name</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>这一次我们看到了输出结果是正常的：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">05</span><span class="p">:</span><span class="mi">56</span><span class="p">:</span><span class="mi">57</span><span class="o">.</span><span class="mi">162</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">64842</span><span class="p">:</span><span class="mi">20459179</span><span class="o">]</span> <span class="n">iOS</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">05</span><span class="p">:</span><span class="mi">56</span><span class="p">:</span><span class="mi">57</span><span class="o">.</span><span class="mi">162</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">64842</span><span class="p">:</span><span class="mi">20459179</span><span class="o">]</span> <span class="n">iOS</span></code></pre></div>
<h3 id="test-3">Test 3</h3>
<p>再来做第三个实验，我们换用 copy 类型的成员 c_name，来替换实验1中的 s_name ，查看一下输出结果。</p>
<p>最后发现输出结果依旧是我们所需要的。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">test3</span> <span class="p">{</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span> <span class="o">=</span> <span class="o">[[</span><span class="no">Person</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSMutableString</span> <span class="o">*</span><span class="nb">name</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSMutableString</span> <span class="ss">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;iOS&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">c_name</span> <span class="o">=</span> <span class="nb">name</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">c_name</span><span class="p">);</span>
    <span class="o">[</span><span class="nb">name</span> <span class="ss">appendString</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot; Source Probe&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">c_name</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">06</span><span class="p">:</span><span class="mo">03</span><span class="p">:</span><span class="mi">40</span><span class="o">.</span><span class="mi">226</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">64922</span><span class="p">:</span><span class="mi">20479646</span><span class="o">]</span> <span class="n">iOS</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">06</span><span class="p">:</span><span class="mo">03</span><span class="p">:</span><span class="mi">40</span><span class="o">.</span><span class="mi">227</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">64922</span><span class="p">:</span><span class="mi">20479646</span><span class="o">]</span> <span class="n">iOS</span></code></pre></div>
<p>做过如上三个实验，或许你会知道对 property 使用 copy 修饰属性的原因了。也就是在一个特定场景下：<strong>当我们通过一个 NSMutableString 对 NSString 变量进行赋值，如果 NSString 的 property 是 strong 类型的时候，就会随着 NSMutableString 类型的变量一起变化</strong>。</p>
<p>这个猜测是正确的。在 <a href="http://stackoverflow.com/questions/11249656/should-an-nsstring-property-under-arc-be-strong-or-copy">stackoverflow</a> 上也对这个场景进行单独的描述。可是原因是什么？继续做下面的实验：</p>
<h3 id="test-4">Test 4</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">test4</span> <span class="p">{</span>
    <span class="no">NSMutableString</span> <span class="o">*</span><span class="n">str</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSMutableString</span> <span class="ss">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;iOS&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str</span><span class="p">);</span>
    <span class="no">NSString</span> <span class="o">*</span><span class="n">str_a</span> <span class="o">=</span> <span class="n">str</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str_a</span><span class="p">);</span>
    <span class="no">NSString</span> <span class="o">*</span><span class="n">str_b</span> <span class="o">=</span> <span class="o">[</span><span class="n">str</span> <span class="n">copy</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str_b</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>输出地址后，我们发现以下结果：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">06</span><span class="p">:</span><span class="mi">15</span><span class="p">:</span><span class="mi">45</span><span class="o">.</span><span class="mi">169</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">65230</span><span class="p">:</span><span class="mi">20515110</span><span class="o">]</span> <span class="mh">0x7faf28429e70</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">06</span><span class="p">:</span><span class="mi">15</span><span class="p">:</span><span class="mi">45</span><span class="o">.</span><span class="mi">170</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">65230</span><span class="p">:</span><span class="mi">20515110</span><span class="o">]</span> <span class="mh">0x7faf28429e70</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mo">06</span><span class="p">:</span><span class="mi">15</span><span class="p">:</span><span class="mi">45</span><span class="o">.</span><span class="mi">170</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">65230</span><span class="p">:</span><span class="mi">20515110</span><span class="o">]</span> <span class="mh">0xa00000000534f693</span></code></pre></div>
<p>发现当令 NSString 对象指针指向一个 NSMutableString 的时候，则会对一个对象进行<strong>深复制</strong>。这也就是我们一直所说的在一个 Class 的成员是 NSString 类型的时候，修饰属性应该使用 copy ，其实就是在使用 mutable 对象进行赋值的时候，防止 mutable 对象的改变从而影响成员变量。从 MRC 的角度来看待修饰属性，若一个属性的关键字为 retain （可等同于 strong ），则在进行指针的指向修改时，如上面的<code>self.one.name = str</code>，其实是执行了<code>self.one.name = [str retain]</code>，而 copy 类型的属性则会执行<code>self.one.name = [str copy]</code>。</p>
<p>而在 Test 2 中，我们的实验是将一个 NSString 对象指向另外一个 NSString 对象，那么如果前者是 copy 的成员，还会进行<strong>深复制</strong>吗？进行下面的 Test 5，我们令 c_name 的修饰变量为 copy。</p>
<h3 id="test-5">Test 5</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">test5</span> <span class="p">{</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span> <span class="o">=</span> <span class="o">[[</span><span class="no">Person</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSString</span> <span class="o">*</span><span class="nb">name</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSMutableString</span> <span class="ss">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;iOS&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">c_name</span> <span class="o">=</span> <span class="nb">name</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">c_name</span><span class="p">);</span>
    <span class="nb">name</span> <span class="o">=</span> <span class="err">@</span><span class="s2">&quot;iOS Source Probe&quot;</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%@&quot;</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">one</span><span class="o">.</span><span class="n">c_name</span><span class="p">);</span>
    <span class="no">NSString</span> <span class="o">*</span><span class="n">str</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSString</span> <span class="ss">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;iOS&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str</span><span class="p">);</span>
    <span class="no">NSString</span> <span class="o">*</span><span class="n">str_a</span> <span class="o">=</span> <span class="n">str</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str_a</span><span class="p">);</span>
    <span class="no">NSString</span> <span class="o">*</span><span class="n">str_b</span> <span class="o">=</span> <span class="o">[</span><span class="n">str</span> <span class="n">copy</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str_b</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<p>发现结果符合猜测：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mi">08</span><span class="p">:</span><span class="mi">09</span><span class="p">:</span><span class="mi">28</span><span class="o">.</span><span class="mi">125</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">66402</span><span class="p">:</span><span class="mi">20671038</span><span class="o">]</span> <span class="n">iOS</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mi">08</span><span class="p">:</span><span class="mi">09</span><span class="p">:</span><span class="mi">28</span><span class="o">.</span><span class="mi">126</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">66402</span><span class="p">:</span><span class="mi">20671038</span><span class="o">]</span> <span class="n">iOS</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mi">08</span><span class="p">:</span><span class="mi">09</span><span class="p">:</span><span class="mi">28</span><span class="o">.</span><span class="mi">126</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">66402</span><span class="p">:</span><span class="mi">20671038</span><span class="o">]</span> <span class="mh">0xa00000000534f693</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mi">08</span><span class="p">:</span><span class="mi">09</span><span class="p">:</span><span class="mi">28</span><span class="o">.</span><span class="mi">126</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">66402</span><span class="p">:</span><span class="mi">20671038</span><span class="o">]</span> <span class="mh">0xa00000000534f693</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mi">08</span><span class="p">:</span><span class="mi">09</span><span class="p">:</span><span class="mi">28</span><span class="o">.</span><span class="mi">126</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">66402</span><span class="p">:</span><span class="mi">20671038</span><span class="o">]</span> <span class="mh">0xa00000000534f693</span></code></pre></div>
<p>从一个 NSString 进行 copy 后赋值，copy 方法仍旧是<strong>浅拷贝</strong>。这个效果就等同于<code>str_b = [str retain]</code>，在 ARC 中即 <code>str_b = str</code>。</p>
<p>那么，如何在这种情况下，让<code>str_b</code>指向一个<code>str</code>的深拷贝呢，答案就是<code>str_b = [str mutableCopy]</code>。这也就是 copy 和 mutableCopy 的区别。</p>
<h2 id="copy--mutablecopy">copy &amp; mutableCopy</h2>
<p>下面我们开始对 copy 和 mutableCopy 原理进行分析。以下也是我的源码学习笔记。</p>
<p>在<a href="git@github.com:RetVal/objc-runtime.git">opensource.apple.com的git仓库中</a>的Runtime源码中有<code>NSObject.mm</code>这个文件，其中有如下方法是关于 copy 的：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="n">copy</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">[</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="nb">self</span> <span class="ss">copyWithZone</span><span class="p">:</span><span class="kp">nil</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="n">mutableCopy</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">[</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="nb">self</span> <span class="ss">mutableCopyWithZone</span><span class="p">:</span><span class="kp">nil</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>发现<code>copy</code>和<code>mutableCopy</code>两个方法只是简单调用了<code>copyWithZone:</code>和<code>mutableCopyWithZone:</code>两个方法。所以有了以下猜想：对于 NSString 和 NSMutableString，Foundation 框架已经为我们实现了 copyWithZone 和 mutableCopyWithZone 的源码。我在<a href="https://searchcode.com">searchcode.com</a>找到了 Hack 版的 NSString 和 NSMutableString 的 Source Code。</p>
<p>在<a href="https://searchcode.com/file/12532490/libFoundation/Foundation/NSString.m">NSString.m</a>中，看到了以下关于 copy 的方法。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="ss">copyWithZone</span><span class="p">:(</span><span class="no">NSZone</span> <span class="o">*</span><span class="p">)</span><span class="n">zone</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="no">NSStringClass</span> <span class="o">==</span> <span class="no">Nil</span><span class="p">)</span>
        <span class="no">NSStringClass</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSString</span> <span class="n">class</span><span class="o">]</span><span class="p">;</span>
    <span class="k">return</span> <span class="no">RETAIN</span><span class="p">(</span><span class="nb">self</span><span class="p">);</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="ss">mutableCopyWithZone</span><span class="p">:(</span><span class="no">NSZone</span><span class="o">*</span><span class="p">)</span><span class="n">zone</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">[[</span><span class="no">NSMutableString</span> <span class="ss">allocWithZone</span><span class="p">:</span><span class="n">zone</span><span class="o">]</span> <span class="ss">initWithString</span><span class="p">:</span><span class="nb">self</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>而在 <a href="https://searchcode.com/file/68838008/jni%20w:%20itoa%20runtime%20and%20allocator/Foundation/NSMutableString.m">NSMutableString.m</a> 中只发现了<code>copyWithZone:</code>和<code>copy:</code>方法，并且它调用了父类的<strong>全能初始化方法（designated initializer）</strong>，所以构造出来的对象是由 NSString 持有的：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="n">copy</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">[[</span><span class="no">NSString</span> <span class="n">alloc</span><span class="o">]</span> <span class="ss">initWithString</span><span class="p">:</span><span class="nb">self</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">-</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="ss">copyWithZone</span><span class="p">:(</span><span class="no">NSZone</span><span class="o">*</span><span class="p">)</span><span class="n">zone</span> <span class="p">{</span>
    <span class="k">return</span> <span class="o">[[</span><span class="no">NSString</span> <span class="ss">allocWithZone</span><span class="p">:</span><span class="n">zone</span><span class="o">]</span> <span class="ss">initWithString</span><span class="p">:</span><span class="nb">self</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>也就是说， NSMutableString 进行 copy 的对象从源码上看也会变成深复制方法。我们做下试验。</p>
<h3 id="test-6">Test 6</h3>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">test6</span> <span class="p">{</span>
    <span class="no">NSMutableString</span> <span class="o">*</span><span class="n">str</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSMutableString</span> <span class="ss">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s2">&quot;iOS&quot;</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str</span><span class="p">);</span>
    <span class="no">NSMutableString</span> <span class="o">*</span><span class="n">str2</span> <span class="o">=</span> <span class="o">[</span><span class="n">str</span> <span class="n">copy</span><span class="o">]</span><span class="p">;</span>
    <span class="no">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s2">&quot;%p&quot;</span><span class="p">,</span> <span class="n">str2</span><span class="p">);</span>
<span class="p">}</span></code></pre></div>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mi">15</span><span class="p">:</span><span class="mi">12</span><span class="p">:</span><span class="mi">12</span><span class="o">.</span><span class="mi">845</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">73658</span><span class="p">:</span><span class="mi">21549553</span><span class="o">]</span> <span class="mh">0x7f96f8410e10</span>
<span class="mi">2016</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">12</span> <span class="mi">15</span><span class="p">:</span><span class="mi">12</span><span class="p">:</span><span class="mi">12</span><span class="o">.</span><span class="mi">846</span> <span class="no">TestCopy</span><span class="o">[</span><span class="mi">73658</span><span class="p">:</span><span class="mi">21549553</span><span class="o">]</span> <span class="mh">0xa00000000534f693</span></code></pre></div>
<p>输出结果如我们所预料的，同样是 NSMutableString 之间的指针传递，即使类型相同，使用了该类型下的 copy 方法，也会变成深复制，因为返回的对象如源码所示，调用了 NSString 的全能初始化方法，并且由一个新的 NSString 持有。那么在 NSMutableString 中使用<code>mutableCopy</code>，可以做到单纯的 retain 操作吗。答案也是否定的，同样是源码中写道，在源码中并没有重写<code>mutableCopy</code>方法，也没有实现<code>mutableCopyWithZone:</code>方法，所以会调用父类的<code>mutableCopyWithZone</code>。而在父类中 <code>mutableCopyWithZone:</code>方法中调用了 NSMutableString 的全局初始化方法，所以依旧是深复制。</p>
<p>以上原则试用于大多数 Foundation 框架中的常用类，如 NSArray 、 NSDictionary 等等。</p>
<h2 id="class--copy-">关于自定义 Class 的 Copy 方法</h2>
<p>对于以上所有试验，我们可以总结一个中关系：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14709874979001.jpg" alt="" /></p>
<p>由此，我们可以总结一下。其实 Copy 对应的也就是类似于 <strong>immutableCopy</strong> 这种感觉，因为通过 copy 出的对象总是不可变的。所以对于一个 Class 中的 mutableCopy 和 copy 的方法命名而言，其实是否有 mutable ，是<strong>针对于返回值而言的</strong>。在 Foundation Framework 中，把拷贝方法称为 copy 而非 immutableCopy 的原因在于，NSCopying 这个基础协议不仅设计给那些具有可变和不可变版本的类来用，而且还要供其他一些没有“可变”和“不可变”之分的类来用。</p>
<p>所以在实现自定义 Class 的 Copy 方法适合，我们需要确定一个问题，应该执行深复制还是浅复制。然后在去实现对应的 <code>copyWithZone:</code> 和 <code>mutableCopyWithZone:</code> 两个方法。这里我不再多论，可以查看 <em>Effective Objective-C 2.0 52 Specific Ways to Improve Your iOS and OS X Programes</em> 的 <em>Tips 22</em> 。</p>
<h2 id="section">对于很多博文的一些疑问</h2>
<p>在很多关于讨论自定义 Class 中的 Copy 方法，都会强调一句：<strong>我们一定要遵循 NSCopying 或 NSMutableCopying 这两个协议</strong>，并且在实例代码中也显式写出了自定义的 Class 是遵循这两个协议的，如下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vi">@interface</span> <span class="ss">XXObject</span><span class="p">:</span> <span class="no">NSObject</span><span class="o">&lt;</span><span class="no">NSCopying</span><span class="p">,</span> <span class="no">NSMutableCopying</span><span class="o">&gt;</span>
<span class="vi">@end</span></code></pre></div>
<p>但是如果我们不显式的写出，我们发现不但没有 crash ，而且结果也是完全一样的。而在 <em><a href="http://zhangbuhuai.com/copy-in-objective-c/">Objective-C copy那些事儿</a></em> 此文中，作者写道：</p>
<blockquote>
  <p>正确的做法是让自定义类遵循NSCopying协议（<strong>NSObject并未遵循该协议</strong>）</p>
</blockquote>
<p>我的猜想是，某次苹果所用的 Foundation 框架升级，使得 NSObject 开始遵循 NSCopying 方法，但是没有去实现（这就好比 c++ 中的 virtual 虚函数）。这里有待考证，如果有朋友知道，欢迎补充这一部分知识，请大家多多指教。</p>
<hr />
<blockquote>
  <p>以上是个人在学习 Foundation 框架的一些源码分析和猜想，如果想了解更多的 <em>iOS Source Probe</em> 系列文章，可以访问 github 仓库 <a href="https://github.com/Desgard/iOS-Source-Probe">iOS-Source-Probe</a>。</p>
</blockquote>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《从经典问题来看 Copy 方法》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/copy/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/copy/&amp;title=《从经典问题来看 Copy 方法》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/copy/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/copy/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>